<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>4.Zone区域之ngZone | 被删的前端游乐场</title>
    <meta name="generator" content="VuePress 1.8.2">
    
    <meta name="description" content="Just playing around">
    
    <link rel="preload" href="/front-end-playground/assets/css/0.styles.6ad2a9ca.css" as="style"><link rel="preload" href="/front-end-playground/assets/js/app.1e2670bf.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/2.38d016d1.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/3.e3f029cb.js" as="script"><link rel="preload" href="/front-end-playground/assets/js/112.466d24d9.js" as="script">
    <link rel="stylesheet" href="/front-end-playground/assets/css/0.styles.6ad2a9ca.css">
  </head>
  <body>
    <div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/front-end-playground/" class="home-link router-link-active"><!----> <span class="site-name">被删的前端游乐场</span></a> <div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/front-end-playground/" class="nav-link">概述</a></div><div class="nav-item"><a href="/front-end-playground/front-end-basic/" class="nav-link">前端领域</a></div><div class="nav-item"><a href="/front-end-playground/vue/" class="nav-link">Vue学习</a></div><div class="nav-item"><a href="/front-end-playground/wxapp/" class="nav-link">小程序学习</a></div><div class="nav-item"><a href="/front-end-playground/front-end-others/" class="nav-link router-link-active">百家齐放</a></div><div class="nav-item"><a href="/front-end-playground/front-end-addon/" class="nav-link">前端的进击</a></div><div class="nav-item"><a href="/front-end-playground/front-end-work/" class="nav-link">前端与工作</a></div><div class="nav-item"><a href="/front-end-playground/faq.html" class="nav-link">FAQ</a></div> <a href="https://github.com/godbasin/front-end-playground" target="_blank" rel="noopener noreferrer" class="repo-link">
    Github
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav></div></header> <div class="sidebar-mask"></div> <aside class="sidebar"><nav class="nav-links"><div class="nav-item"><a href="/front-end-playground/" class="nav-link">概述</a></div><div class="nav-item"><a href="/front-end-playground/front-end-basic/" class="nav-link">前端领域</a></div><div class="nav-item"><a href="/front-end-playground/vue/" class="nav-link">Vue学习</a></div><div class="nav-item"><a href="/front-end-playground/wxapp/" class="nav-link">小程序学习</a></div><div class="nav-item"><a href="/front-end-playground/front-end-others/" class="nav-link router-link-active">百家齐放</a></div><div class="nav-item"><a href="/front-end-playground/front-end-addon/" class="nav-link">前端的进击</a></div><div class="nav-item"><a href="/front-end-playground/front-end-work/" class="nav-link">前端与工作</a></div><div class="nav-item"><a href="/front-end-playground/faq.html" class="nav-link">FAQ</a></div> <a href="https://github.com/godbasin/front-end-playground" target="_blank" rel="noopener noreferrer" class="repo-link">
    Github
    <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav>  <ul class="sidebar-links"><li><section class="sidebar-group collapsable depth-0" style="padding-top:10px;"><div class="kitty-main" data-v-2b653b36><span class="stand" data-v-2b653b36></span> <div class="cat" data-v-2b653b36><div class="body" data-v-2b653b36></div> <div class="head" data-v-2b653b36><div class="ear" data-v-2b653b36></div> <div class="ear" data-v-2b653b36></div></div> <div class="face" data-v-2b653b36><div class="nose" data-v-2b653b36></div> <div class="whisker-container" data-v-2b653b36><div class="whisker" data-v-2b653b36></div> <div class="whisker" data-v-2b653b36></div></div> <div class="whisker-container" data-v-2b653b36><div class="whisker" data-v-2b653b36></div> <div class="whisker" data-v-2b653b36></div></div></div> <div class="tail-container" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36><div class="tail" data-v-2b653b36></div></div></div></div></div></div></div></div></div></div> <p class="sidebar-heading open"><span>Angular框架解读</span> <span class="arrow down"></span></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-0-prestart.html" class="sidebar-link">0.预热篇</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-metadata.html" class="sidebar-link">1.元数据和装饰器</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-dom-define.html" class="sidebar-link">2.视图抽象定义</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-zonejs.html" class="sidebar-link">3.Zone区域之zone.js</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-zone-ngzone.html" aria-current="page" class="active sidebar-link">4.Zone区域之ngZone</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-zone-ngzone.html#ngzone" class="sidebar-link">NgZone</a></li><li class="sidebar-sub-header"><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-zone-ngzone.html#总结" class="sidebar-link">总结</a></li></ul></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-module.html" class="sidebar-link">5.模块化组织</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-1-basic-concepts.html" class="sidebar-link">6.依赖注入的基本概念</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-2-hierarchical-di.html" class="sidebar-link">7.多级依赖注入设计</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-di-3-bootstrap.html" class="sidebar-link">8.依赖注入的引导过程</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-ivy-0-design.html" class="sidebar-link">9.Ivy编译器整体设计</a></li><li><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-ivy-1-view-data-and-node-injector.html" class="sidebar-link">10.Ivy编译器的视图数据和依赖解析</a></li></ul></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>玩转 Angular</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>试试 Cycle.js</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>D3.js 和 Tree</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>试试 Three.js</span> <span class="arrow right"></span></p> <!----></section></li><li><section class="sidebar-group collapsable depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>BOX2D.js 文档与教程</span> <span class="arrow right"></span></p> <!----></section></li></ul> </aside> <main class="page"> <div class="theme-default-content content__default"><p>作为“为大型前端项目”而设计的前端框架，Angular 其实有许多值得参考和学习的设计，本系列主要用于研究这些设计和功能的实现原理。本文主要围绕 Angular 中的 NgZone 的设计和实现来介绍。</p> <p>上一篇我们介绍了 <a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-zonejs.html">zone.js</a>，它解决了很多 Javascript 异步编程时上下文的问题。</p> <p>NgZone 基于 zone.js 集成了适用于 Angular 框架的一些能力。其中，对于 Angular 中的数据变更检测（脏检查）的性能优化，则主要依赖了 NgZone 的设计，我们一起来看一下。</p> <h2 id="ngzone"><a href="#ngzone" class="header-anchor">#</a> NgZone</h2> <p>虽然 zone.js 可以监视同步和异步操作的所有状态，但 Angular 还提供了一项名为 NgZone 的服务。</p> <p>NgZone 是一种用于在 Angular 区域内部或外部执行工作的可注射服务，对于不需要 Angular 处理 UI 更新或错误处理的异步任务来说，进行了性能优化的工作。</p> <h3 id="ngzone-设计"><a href="#ngzone-设计" class="header-anchor">#</a> NgZone 设计</h3> <p>我们来看看 NgZone 的实现：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">NgZone</span> <span class="token punctuation">{</span>
  <span class="token keyword">readonly</span> hasPendingMacrotasks<span class="token operator">:</span> <span class="token builtin">boolean</span> <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
  <span class="token keyword">readonly</span> hasPendingMicrotasks<span class="token operator">:</span> <span class="token builtin">boolean</span> <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
  <span class="token keyword">readonly</span> isStable<span class="token operator">:</span> <span class="token builtin">boolean</span> <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
  <span class="token keyword">readonly</span> onUnstable<span class="token operator">:</span> EventEmitter<span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">&gt;</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">EventEmitter</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">readonly</span> onMicrotaskEmpty<span class="token operator">:</span> EventEmitter<span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">&gt;</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">EventEmitter</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">readonly</span> onStable<span class="token operator">:</span> EventEmitter<span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">&gt;</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">EventEmitter</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token keyword">readonly</span> onError<span class="token operator">:</span> EventEmitter<span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">&gt;</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">EventEmitter</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    enableLongStackTrace <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
    shouldCoalesceEventChangeDetection <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">,</span>
    shouldCoalesceRunChangeDetection <span class="token operator">=</span> <span class="token boolean">false</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token operator">...</span>
    <span class="token comment">// 在当前区域创建子区域，作为 Angular 区域</span>
    <span class="token function">forkInnerZoneWithAngularBehavior</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// 是否在 Angular 区域里</span>
  <span class="token keyword">static</span> <span class="token function">isInAngularZone</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">boolean</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> Zone<span class="token punctuation">.</span>current<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">'isAngularZone'</span><span class="token punctuation">)</span> <span class="token operator">===</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// 在 Angular 区域内同步执行 fn 函数，并返回该函数返回的值</span>
  <span class="token comment">// 通过 run 运行可让在 Angular 区域之外执行的任务重新进入 Angular 区域</span>
  <span class="token generic-function"><span class="token function">run</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token operator">...</span>args<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token constant">T</span><span class="token punctuation">,</span> applyThis<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">,</span> applyArgs<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token constant">T</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token keyword">as</span> <span class="token builtin">any</span> <span class="token keyword">as</span> NgZonePrivate<span class="token punctuation">)</span><span class="token punctuation">.</span>_inner<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>fn<span class="token punctuation">,</span> applyThis<span class="token punctuation">,</span> applyArgs<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// 在 Angular 区域内作为任务同步执行 fn 函数，并返回该函数返回的值</span>
  <span class="token generic-function"><span class="token function">runTask</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token operator">...</span>args<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token constant">T</span><span class="token punctuation">,</span> applyThis<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">,</span> applyArgs<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> name<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token constant">T</span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> zone <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token keyword">as</span> <span class="token builtin">any</span> <span class="token keyword">as</span> NgZonePrivate<span class="token punctuation">)</span><span class="token punctuation">.</span>_inner<span class="token punctuation">;</span>
    <span class="token keyword">const</span> task <span class="token operator">=</span> zone<span class="token punctuation">.</span><span class="token function">scheduleEventTask</span><span class="token punctuation">(</span><span class="token string">'NgZoneEvent: '</span> <span class="token operator">+</span> name<span class="token punctuation">,</span> fn<span class="token punctuation">,</span> <span class="token constant">EMPTY_PAYLOAD</span><span class="token punctuation">,</span> noop<span class="token punctuation">,</span> noop<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> zone<span class="token punctuation">.</span><span class="token function">runTask</span><span class="token punctuation">(</span>task<span class="token punctuation">,</span> applyThis<span class="token punctuation">,</span> applyArgs<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
      zone<span class="token punctuation">.</span><span class="token function">cancelTask</span><span class="token punctuation">(</span>task<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// 与 run 相同，除了同步错误是通过 onError 捕获并转发的，而不是重新抛出</span>
  <span class="token generic-function"><span class="token function">runGuarded</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token operator">...</span>args<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token constant">T</span><span class="token punctuation">,</span> applyThis<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">,</span> applyArgs<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token constant">T</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token keyword">as</span> <span class="token builtin">any</span> <span class="token keyword">as</span> NgZonePrivate<span class="token punctuation">)</span><span class="token punctuation">.</span>_inner<span class="token punctuation">.</span><span class="token function">runGuarded</span><span class="token punctuation">(</span>fn<span class="token punctuation">,</span> applyThis<span class="token punctuation">,</span> applyArgs<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
  <span class="token comment">// 在 Angular 区域外同步执行 fn 函数，并返回该函数返回的值</span>
  <span class="token generic-function"><span class="token function">runOutsideAngular</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">&gt;</span></span></span><span class="token punctuation">(</span><span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token operator">...</span>args<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token constant">T</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token constant">T</span> <span class="token punctuation">{</span>
    <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token keyword">as</span> <span class="token builtin">any</span> <span class="token keyword">as</span> NgZonePrivate<span class="token punctuation">)</span><span class="token punctuation">.</span>_outer<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>fn<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>NgZone 基于 zone.js 之上再做了一层封装，通过<code>fork</code>创建出子区域作为 Angular 区域：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">function</span> <span class="token function">forkInnerZoneWithAngularBehavior</span><span class="token punctuation">(</span>zone<span class="token operator">:</span> NgZonePrivate<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token operator">...</span>
  <span class="token comment">// 创建子区域，为 Angular 区域</span>
  zone<span class="token punctuation">.</span>_inner <span class="token operator">=</span> zone<span class="token punctuation">.</span>_inner<span class="token punctuation">.</span><span class="token function">fork</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    name<span class="token operator">:</span> <span class="token string">'angular'</span><span class="token punctuation">,</span>
    properties<span class="token operator">:</span> <span class="token operator">&lt;</span><span class="token builtin">any</span><span class="token operator">&gt;</span><span class="token punctuation">{</span><span class="token string">'isAngularZone'</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token operator">...</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>除此之外，NgZone 里添加了用于表示没有微任务或宏任务的属性<code>isStable</code>，可用于状态的检测。另外，NgZone 还定义了四个事件：</p> <ul><li><code>onUnstable</code>: 通知代码何时进入 Angular Zone，首先会在 VM Turn 上触发</li> <li><code>onMicrotaskEmpty</code>: 通知当前的 VM Turn 中没有更多的微任务排队。这是 Angular 进行更改检测的提示，它可能会排队更多的微任务（此事件可在每次 VM 翻转时触发多次）</li> <li><code>onStable</code>: 通知最后一个<code>onMicrotaskEmpty</code>已运行并且没有更多的微任务，这意味着即将放弃 VM 转向（此事件仅被调用一次）</li> <li><code>onError</code>: 通知已传送错误</li></ul> <p>上一节我们讲到，zone.js 处理了大多数异步 API，比如<code>setTimeout()</code>、<code>Promise.then()</code>和<code>addEventListener()</code>等。对于一些 zone.js 无法处理的第三方 API，NgZone 服务的<code>run()</code>方法可允许在 angular Zone 中执行函数。</p> <p>通过使用 Angular Zone，函数中的所有异步操作会在正确的时间自动触发变更检测。</p> <h3 id="自动触发变更检测"><a href="#自动触发变更检测" class="header-anchor">#</a> 自动触发变更检测</h3> <p>当 NgZone 满足以下条件时，会创建一个名为 angular 的 Zone 来自动触发变更检测：</p> <ul><li>当执行同步或异步功能时（zone.js 内置变更检测，最终会通过<code>onMicrotaskEmpty</code>来触发）</li> <li>已经没有已计划的 Microtask（<code>onMicrotaskEmpty</code>）</li></ul> <p><code>onMicrotaskEmpty</code>条件的触发监听，以及检测逻辑位于<code>ApplicationRef</code>中：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token decorator"><span class="token at operator">@</span><span class="token function">Injectable</span></span><span class="token punctuation">(</span><span class="token punctuation">)</span>
<span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">ApplicationRef</span> <span class="token punctuation">{</span>
  <span class="token operator">...</span>
  <span class="token function">constructor</span><span class="token punctuation">(</span>
      <span class="token keyword">private</span> _zone<span class="token operator">:</span> NgZone<span class="token punctuation">,</span> <span class="token keyword">private</span> _injector<span class="token operator">:</span> Injector<span class="token punctuation">,</span> <span class="token keyword">private</span> _exceptionHandler<span class="token operator">:</span> ErrorHandler<span class="token punctuation">,</span>
      <span class="token keyword">private</span> _componentFactoryResolver<span class="token operator">:</span> ComponentFactoryResolver<span class="token punctuation">,</span>
      <span class="token keyword">private</span> _initStatus<span class="token operator">:</span> ApplicationInitStatus<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// Microtask 为空时，触发变更检测</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>_onMicrotaskEmptySubscription <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>_zone<span class="token punctuation">.</span>onMicrotaskEmpty<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
      <span class="token function-variable function">next</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>_zone<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
          <span class="token comment">// tick 为变更检测的逻辑，会重新进行 template 的计算和渲染</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">tick</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token operator">...</span>
<span class="token punctuation">}</span>
</code></pre></div><p>我们来看看，在什么时候会触发<code>onMicrotaskEmpty</code>事件：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">function</span> <span class="token function">checkStable</span><span class="token punctuation">(</span>zone<span class="token operator">:</span> NgZonePrivate<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">if</span> <span class="token punctuation">(</span>zone<span class="token punctuation">.</span>_nesting <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>zone<span class="token punctuation">.</span>hasPendingMicrotasks <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>zone<span class="token punctuation">.</span>isStable<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
      zone<span class="token punctuation">.</span>_nesting<span class="token operator">++</span><span class="token punctuation">;</span>
      zone<span class="token punctuation">.</span>onMicrotaskEmpty<span class="token punctuation">.</span><span class="token function">emit</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
      zone<span class="token punctuation">.</span>_nesting<span class="token operator">--</span><span class="token punctuation">;</span>
      <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>zone<span class="token punctuation">.</span>hasPendingMicrotasks<span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">try</span> <span class="token punctuation">{</span>
          zone<span class="token punctuation">.</span><span class="token function">runOutsideAngular</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> zone<span class="token punctuation">.</span>onStable<span class="token punctuation">.</span><span class="token function">emit</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
          zone<span class="token punctuation">.</span>isStable <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre></div><p>当<code>onInvokeTask</code>和<code>onInvoke</code>两个钩子被触发时，微任务队列中可能会发生变化，因此 Angular 必须在每次钩子被触发时运行检查。除此之外，<code>onHasTask</code>挂钩还用于执行检查，因为它跟踪整个队列更改：</p> <div class="language-ts extra-class"><pre class="language-ts"><code><span class="token keyword">function</span> <span class="token function">forkInnerZoneWithAngularBehavior</span><span class="token punctuation">(</span>zone<span class="token operator">:</span> NgZonePrivate<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token keyword">const</span> <span class="token function-variable function">delayChangeDetectionForEventsDelegate</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
    <span class="token comment">// delayChangeDetectionForEvents 内部调用了 checkStable()</span>
    <span class="token function">delayChangeDetectionForEvents</span><span class="token punctuation">(</span>zone<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
  zone<span class="token punctuation">.</span>_inner <span class="token operator">=</span> zone<span class="token punctuation">.</span>_inner<span class="token punctuation">.</span><span class="token function">fork</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    <span class="token operator">...</span>
    onInvokeTask<span class="token operator">:</span>
        <span class="token punctuation">(</span>delegate<span class="token operator">:</span> ZoneDelegate<span class="token punctuation">,</span> current<span class="token operator">:</span> Zone<span class="token punctuation">,</span> target<span class="token operator">:</span> Zone<span class="token punctuation">,</span> task<span class="token operator">:</span> Task<span class="token punctuation">,</span> applyThis<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">,</span> applyArgs<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">any</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
          <span class="token operator">...</span>
          <span class="token comment">// 进行检测</span>
          <span class="token function">delayChangeDetectionForEventsDelegate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>

    onInvoke<span class="token operator">:</span>
        <span class="token punctuation">(</span>delegate<span class="token operator">:</span> ZoneDelegate<span class="token punctuation">,</span> current<span class="token operator">:</span> Zone<span class="token punctuation">,</span> target<span class="token operator">:</span> Zone<span class="token punctuation">,</span> callback<span class="token operator">:</span> <span class="token builtin">Function</span><span class="token punctuation">,</span> applyThis<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">,</span> applyArgs<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> source<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">any</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
          <span class="token operator">...</span>
          <span class="token comment">// 进行检测</span>
          <span class="token function">delayChangeDetectionForEventsDelegate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>

    <span class="token function-variable function">onHasTask</span><span class="token operator">:</span>
        <span class="token punctuation">(</span>delegate<span class="token operator">:</span> ZoneDelegate<span class="token punctuation">,</span> current<span class="token operator">:</span> Zone<span class="token punctuation">,</span> target<span class="token operator">:</span> Zone<span class="token punctuation">,</span> hasTaskState<span class="token operator">:</span> HasTaskState<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
          <span class="token operator">...</span>
          <span class="token keyword">if</span> <span class="token punctuation">(</span>current <span class="token operator">===</span> target<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token comment">// 只检查当前区域的任务</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>hasTaskState<span class="token punctuation">.</span>change <span class="token operator">==</span> <span class="token string">'microTask'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
              zone<span class="token punctuation">.</span>_hasPendingMicrotasks <span class="token operator">=</span> hasTaskState<span class="token punctuation">.</span>microTask<span class="token punctuation">;</span>
              <span class="token function">updateMicroTaskStatus</span><span class="token punctuation">(</span>zone<span class="token punctuation">)</span><span class="token punctuation">;</span>
              <span class="token comment">// 跟踪 MicroTask 队列，并进行检查</span>
              <span class="token function">checkStable</span><span class="token punctuation">(</span>zone<span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token punctuation">}</span>
            <span class="token operator">...</span>
          <span class="token punctuation">}</span>
        <span class="token punctuation">}</span><span class="token punctuation">,</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre></div><p>默认情况下，所有异步操作都在 Angular Zone 内，这会自动触发变更检测。</p> <p>另一个常见的情况是我们不想触发变更检测（比如不希望像<code>scroll</code>等事件过于频繁地进行变更检测，从而导致性能问题），此时可以使用 NgZone 的<code>runOutsideAngular()</code>方法。</p> <p>zone.js 能帮助 Angular 知道何时要触发变更检测，使得开发人员专注于应用开发。默认情况下，zone.js 已加载且无需其他配置即可工作。如果希望选择自己触发变更检测，则可以通过禁用 zone.js 的方式来处理。</p> <h2 id="总结"><a href="#总结" class="header-anchor">#</a> 总结</h2> <p>本文介绍了 NgZone 在 zone.js 的基础上进行了封装，从而使得在 Angular Zone 内函数中的所有异步操作可以在正确的时间自动触发变更检测。</p> <p>可以根据自身的需要，使用 NgZone 的<code>runOutsideAngular()</code>方法减少变更检测，也可以通过禁用 zone.js 的方式，来自己实现变更检测的逻辑。</p> <h3 id="参考"><a href="#参考" class="header-anchor">#</a> 参考</h3> <ul><li><a href="https://angular.cn/guide/zone" target="_blank" rel="noopener noreferrer">Angular-NgZone<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li> <li><a href="https://indepth.dev/posts/1059/do-you-still-think-that-ngzone-zone-js-is-required-for-change-detection-in-angular" target="_blank" rel="noopener noreferrer">Do you still think that NgZone (zone.js) is required for change detection in Angular?<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></li></ul></div> <!----> <footer class="page-edit"><div class="edit-link"><a href="https://github.com/godbasin/front-end-playground/edit/sourcecode/docs/front-end-others/deep-into-angular/angular-design-zone-ngzone.md" target="_blank" rel="noopener noreferrer">帮阿猪改善此页面！</a> <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></div> <!----> <blockquote>部分文章中使用了一些网站的截图，如果涉及侵权，请告诉我删一下谢谢~</blockquote> <div style="margin-top:30px;"><div class="el-row" style="margin-left:-10px;margin-right:-10px;"><div class="el-col el-col-24 el-col-sm-0 el-col-md-2 el-col-lg-4" style="padding-left:10px;padding-right:10px;display:block;"><div style="width:1px;height:1px;"></div></div> <div class="el-col el-col-24 el-col-sm-24 el-col-md-18 el-col-lg-16" style="padding-left:10px;padding-right:10px;"><div class="el-card box-card is-always-shadow"><div class="el-card__header"><div class="clearfix"><span>温馨提示喵</span></div></div><div class="el-card__body"> <div class="el-row" style="margin-left:-10px;margin-right:-10px;"><div class="el-col el-col-24 el-col-xs-24 el-col-sm-12" style="padding-left:10px;padding-right:10px;"><div class="el-image"><div class="image-slot"><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/assets/img/loading.gif" style="width:100%;"></div><!----></div></div> <div class="el-col el-col-24 el-col-xs-24 el-col-sm-12" style="padding-left:10px;padding-right:10px;"><div class="copyright-text"><div>本文版权归作者所有，欢迎转载，但未经作者同意必须保留此段声明，且在文章页面明显位置给出原文连接，否则保留追究法律责任的权利。</div> <div>出处：被删的前端游乐场</div> <div>作者：<a href="https://github.com/godbasin" target="_blank">被删</a></div></div></div></div></div></div></div></div></div></footer> <div class="page-nav"><p class="inner"><span class="prev">
        ←
        <a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-zonejs.html" class="prev">
          3.Zone区域之zone.js
        </a></span> <span class="next"><a href="/front-end-playground/front-end-others/deep-into-angular/angular-design-module.html">
          5.模块化组织
        </a>
        →
      </span></p></div>  <div class="gitalk-container theme-default-content"><div id="gitalk-container" class="content"></div></div></main> <div id="kitty-container"><span><div role="tooltip" id="el-popover-2796" aria-hidden="true" class="el-popover el-popper" style="width:undefinedpx;display:none;"><!----><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/2code2.jpg" class="image"> <div class="text">牡羊猪的猫粮罐</div> </div><span class="el-popover__reference-wrapper"><div id="kitty" style="background:url(https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/assets/img/kitty1.svg);"></div></span></span> <div class="el-dialog__wrapper" style="display:none;"><div role="dialog" aria-modal="true" aria-label="牡羊猪是这样渐渐胖成猪的喵（点击图片可以切换噢）" class="el-dialog" style="margin-top:15vh;"><div class="el-dialog__header"><span class="el-dialog__title">牡羊猪是这样渐渐胖成猪的喵（点击图片可以切换噢）</span><button type="button" aria-label="Close" class="el-dialog__headerbtn"><i class="el-dialog__close el-icon el-icon-close"></i></button></div><!----><!----></div></div></div></div><div class="global-ui"></div></div>
    <script src="/front-end-playground/assets/js/app.1e2670bf.js" defer></script><script src="/front-end-playground/assets/js/2.38d016d1.js" defer></script><script src="/front-end-playground/assets/js/3.e3f029cb.js" defer></script><script src="/front-end-playground/assets/js/112.466d24d9.js" defer></script>
  </body>
</html>
